package com.frikinet.lifeguard.gui;

import java.awt.Component;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Vector;

import javax.swing.JLabel;
import javax.swing.JTree;
import javax.swing.ToolTipManager;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

import com.frikinet.lifeguard.App;
import com.frikinet.lifeguard.events.MonthSelectedListener;
import com.frikinet.lifeguard.events.PostSelectedEvent;
import com.frikinet.lifeguard.events.PostSelectedListener;
import com.frikinet.lifeguard.events.UserSelectedEvent;
import com.frikinet.lifeguard.events.UserSelectedListener;
import com.frikinet.lifeguard.events.YearMonthSelectedEvent;
import com.frikinet.lifeguard.events.YearSelectedListener;
import com.frikinet.lifeguard.listeners.AppModelStateListener;
import com.frikinet.lifeguard.model.Post;
import com.frikinet.lifeguard.model.User;

public class CalendarTree extends JTree
implements TreeSelectionListener, PostSelectedListener, UserSelectedListener, YearSelectedListener, MonthSelectedListener
{
	private static final long serialVersionUID = 1L;
	
	private CalendarTreeModel model;

	public CalendarTree()
	{
		super();
		setModel((model = new CalendarTreeModel()));
		setCellRenderer(new CalendarTreeRenderer());
		setRootVisible(false);
		ToolTipManager.sharedInstance().registerComponent(this);
		addTreeSelectionListener(this);
		App.events.addUserSelectedListener(this);
		App.events.addYearSelectedListener(this);
		App.events.addMonthSelectedListener(this);
		App.events.addPostSelectedListener(this);
		setExpandsSelectedPaths(true);
		setShowsRootHandles(true);
	}

	@Override
	public void valueChanged(TreeSelectionEvent e)
	{
		CalendarNode node = (CalendarNode)e.getPath().getLastPathComponent();
		switch(node.getType())
		{
			case USER_NODE:
				App.events.fireUserSelected(new UserSelectedEvent(this, (User)node.getValue()));
				break;
			case YEAR_NODE:
				App.events.fireYearSelected(new YearMonthSelectedEvent(this, node.getUser(), (Integer)node.getValue()));
				break;
			case MONTH_NODE:
				String yearmonth = node.getValue().toString();
				int year = Integer.parseInt(yearmonth.substring(0, 4));
				int month = Integer.parseInt(yearmonth.substring(4, 6));
				App.events.fireMonthSelected(new YearMonthSelectedEvent(this, node.getUser(), year, month));
				break;
			case POST_NODE:
				App.events.firePostSelected(new PostSelectedEvent(this, (Post)node.getValue()));
				break;
		}
	}
	
	private void setSelection(TreePath path)
	{
		if(getSelectionPath() == null || !getSelectionPath().getLastPathComponent().equals(path.getLastPathComponent()))
		{
			setSelectionPath(path);
			makeVisible(path);
			scrollPathToVisible(path);
		}
	}
	
	@Override
	public void userSelected(UserSelectedEvent event)
	{
		if(event.getSource() != this)
		{
			TreePath path = new TreePath(new CalendarNode[] {
					model.root,
					new CalendarNode(CalendarNodeType.USER_NODE, event.getUser(), event.getUser())
			});
			setSelection(path);
		}
	}
	
	@Override
	public void yearSelected(YearMonthSelectedEvent event)
	{
		if(event.getSource() != this)
		{
			TreePath path = new TreePath(new CalendarNode[] {
					model.root,
					new CalendarNode(CalendarNodeType.USER_NODE, event.getUser(), event.getUser()),
					new CalendarNode(CalendarNodeType.YEAR_NODE, event.getUser(), event.getYear())
			});
			setSelection(path);
		}
	}
	
	@Override
	public void monthSelected(YearMonthSelectedEvent event)
	{
		if(event.getSource() != this)
		{
			TreePath path = new TreePath(new CalendarNode[] {
					model.root,
					new CalendarNode(CalendarNodeType.USER_NODE, event.getUser(), event.getUser()),
					new CalendarNode(CalendarNodeType.YEAR_NODE, event.getUser(), event.getYear()),
					new CalendarNode(CalendarNodeType.MONTH_NODE, event.getUser(), Integer.valueOf(String.format("%d%02d", event.getYear(), event.getMonth())))
			});
			setSelection(path);
		}
	}
	
	@Override
	public void postSelected(PostSelectedEvent event)
	{
		if(event.getSource() != this)
		{
			Post post = event.getPost();
			TreePath path = new TreePath(new CalendarNode[] {
					model.root,
					new CalendarNode(CalendarNodeType.USER_NODE, post.getUser(), post.getUser()),
					new CalendarNode(CalendarNodeType.YEAR_NODE, post.getUser(), Integer.valueOf(model.toYear.format(post.getDate()))),
					new CalendarNode(CalendarNodeType.MONTH_NODE, post.getUser(), Integer.valueOf(model.toYearMonth.format(post.getDate()))),
					new CalendarNode(CalendarNodeType.POST_NODE, post.getUser(), post)
			});
			setSelection(path);
		}
	}

	public enum CalendarNodeType
	{
		ROOT, USER_NODE, YEAR_NODE, MONTH_NODE, POST_NODE
	}

	public class CalendarNode
	{
		private Object value;
		private CalendarNodeType type;
		private User user;

		public CalendarNode(CalendarNodeType type, User user, Object value)
		{
			this.value = value;
			this.user = user;
			this.type = type;
		}

		public Object getValue()
		{
			return value;
		}

		public void setValue(Object value)
		{
			this.value = value;
		}

		public CalendarNodeType getType()
		{
			return type;
		}

		public void setType(CalendarNodeType type)
		{
			this.type = type;
		}

		public User getUser()
		{
			return user;
		}

		public void setUser(User user)
		{
			this.user = user;
		}

		@Override
		public boolean equals(Object obj)
		{
			if(obj instanceof CalendarNode)
			{
				CalendarNode node = (CalendarNode)obj;
				if(getType()==node.getType())
				{
					switch(getType())
					{
						case ROOT:
							return true;
						case YEAR_NODE:
						case MONTH_NODE:
							return getUser().equals(node.getUser()) && getValue().equals(node.getValue());
						default:
							return getValue().equals(node.getValue());
					}
				}
				else
				{
					return false;
				}
			}
			return false;
		}
	}

	public class CalendarTreeModel implements TreeModel, AppModelStateListener
	{
		private final SimpleDateFormat toYear = new SimpleDateFormat("yyyy");
		private final SimpleDateFormat toYearMonth = new SimpleDateFormat("yyyyMM");
		private final CalendarNode root = new CalendarNode(CalendarNodeType.ROOT, null, "");

		private Vector<TreeModelListener> treeModelListeners = new Vector<TreeModelListener>();
		
		public CalendarTreeModel()
		{
			App.events.addModelStateListener(this);
		}

		@Override
		public void modelChanged()
		{
			TreePath oldPath = CalendarTree.this.getSelectionPath();
			CalendarTree.this.setSelectionPath(new TreePath(root));
			fireTreeStructureChanged();
			if(oldPath != null && oldPath.getPath().length >= 2)
			{
				User user = ((CalendarNode)oldPath.getPath()[1]).getUser();
				if(App.users.contains(user))
				{
					CalendarTree.this.setSelectionPath(oldPath);
				}
			}
		}

		protected void fireTreeStructureChanged()
		{
			TreeModelEvent e = new TreeModelEvent(this, new Object[] { getRoot() });
			for (TreeModelListener tml : treeModelListeners)
			{
				tml.treeStructureChanged(e);
			}
		}

		@Override
		public void addTreeModelListener(TreeModelListener l)
		{
			treeModelListeners.addElement(l);
		}

		public List<CalendarNode> getChildren(Object parent)
		{
			List<CalendarNode> nodes = new ArrayList<CalendarNode>();
			CalendarNode node = (CalendarNode) parent;
			switch (node.getType())
			{
			case ROOT:
				for (User user : App.users)
				{
					nodes.add(new CalendarNode(CalendarNodeType.USER_NODE, user, user));
				}
				break;
			case USER_NODE:
				User user = (User) node.getValue();
				SortedSet<Integer> years = new TreeSet<Integer>();
				for (Post post : user.getPosts())
				{
					years.add(Integer.parseInt(toYear.format(post.getDate())));
				}
				for (Integer year : years)
				{
					nodes.add(new CalendarNode(CalendarNodeType.YEAR_NODE, user, year));
				}
				break;
			case YEAR_NODE:
				int year = ((Integer) node.getValue()).intValue();
				SortedSet<Integer> months = new TreeSet<Integer>();
				for (Post post : node.getUser().getPosts())
				{
					if (Integer.parseInt(toYear.format(post.getDate())) == year)
					{
						months.add(Integer.parseInt(toYearMonth.format(post.getDate())));
					}
				}
				for (Integer month : months)
				{
					nodes.add(new CalendarNode(CalendarNodeType.MONTH_NODE, node.getUser(), month));
				}
				break;
			case MONTH_NODE:
				int month = ((Integer) node.getValue()).intValue();
				for (Post post : node.getUser().getPosts())
				{
					if (Integer.parseInt(toYearMonth.format(post.getDate())) == month)
					{
						nodes.add(new CalendarNode(CalendarNodeType.POST_NODE, node.getUser(), post));
					}
				}
				break;
			case POST_NODE:
				break;
			}
			return nodes;
		}

		@Override
		public Object getChild(Object parent, int index)
		{
			return getChildren(parent).get(index);
		}

		@Override
		public int getChildCount(Object parent)
		{
			return getChildren(parent).size();
		}

		@Override
		public int getIndexOfChild(Object parent, Object child)
		{
			return getChildren(parent).indexOf(child);
		}

		@Override
		public Object getRoot()
		{
			return root;
		}

		@Override
		public boolean isLeaf(Object node)
		{
			return ((CalendarNode) node).getType() == CalendarNodeType.POST_NODE;
		}

		@Override
		public void removeTreeModelListener(TreeModelListener l)
		{
			treeModelListeners.removeElement(l);
		}

		@Override
		public void valueForPathChanged(TreePath path, Object newValue)
		{

		}
	}
	
	public class CalendarTreeRenderer extends DefaultTreeCellRenderer
	{
		private static final long serialVersionUID = 1L;
		private final SimpleDateFormat yearMonth = new SimpleDateFormat("yyyyMM");
		private final SimpleDateFormat month = new SimpleDateFormat("MM - MMMM");
		private final SimpleDateFormat day = new SimpleDateFormat("dd - EEEE");

		@Override
		public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus)
		{
			JLabel label = (JLabel)super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
			CalendarNode node = (CalendarNode)value;
			switch(node.getType())
			{
				case USER_NODE:
					label.setText(node.getUser().getName());
					break;
				case YEAR_NODE:
					label.setText(node.getValue().toString());
					break;
				case MONTH_NODE:
					try
					{
						Date date = yearMonth.parse(node.getValue().toString());
						label.setText(month.format(date));
					}
					catch(ParseException e)
					{
						
					}
					break;
				case POST_NODE:
					Post post = (Post)node.getValue();
					label.setText(day.format(post.getDate()));
					if(post.getTitle() != null && !post.getTitle().trim().equals(""))
					{
						label.setToolTipText(post.getTitle());
					}
					break;
			}
			return label;
		}
		
	}
}
